/**
 * Copyright 2010 VTT Finnish Technical Research Centre
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.osami.syncagent.server;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Vector;

import org.osami.syncagent.SyncAgentService;
import org.osami.syncagent.impl.Activator;

/**
 * Singular server thread which listens clients and invokes listener thread one per client.
 * Server invokes a new thread when ever client connects to it.  
 * @author elekko
 *
 */
public class SyncAgentServer{
	private Vector<URI> clientList;
	public SyncAgentService localSyncAgent;
	public int maxConnections = 55;
	private Thread bct;
	private SyncAgentBroadCastClient bcServer;
	private SyncAgentServerThread serverThread;
	//Vector SyncAgentServerListenerTCPIP

	public SyncAgentServer(SyncAgentService activator) {
		localSyncAgent = activator;
		clientList = new Vector();
		serverThread = new SyncAgentServerThread(this);
		Thread t = new Thread(serverThread);
        t.start();
        sendBroadCastMessage();
	}

	public void sendBroadCastMessage()
	{
		/**
		 * Send a broadcast message to network that new server has been created
		 */
        //InetAddress group = InetAddress.getByName(InetAddress.getLocalHost());
        InetAddress bcAddr = null;
        //DatagramSocket socket = null;
		try {
			bcAddr = getBroadCastAddress();
			//socket = new DatagramSocket(BC_PORT, bcAddr);
			//socket.setBroadcast(true);
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		/**
		 *  Start broadcast listener thread
		 */
		bcServer =  new SyncAgentBroadCastClient(bcAddr.getHostAddress(), this);
		bct = new Thread(bcServer);
		bct.start();
		System.out.println("SyncAgentActivator: Broadcast thread started");
        DatagramPacket packet;
        byte[] buf = new byte[256];
        String dString = "";
		try {
			dString = "register;osami://"+getLocalAddress().getHostAddress() + ":" + Activator.SERVER_PORT;
			System.out.println("SyncAgentActivator: Broadcast message: " + dString);
			buf = dString.getBytes();
			packet = new DatagramPacket(buf, buf.length, bcAddr, Activator.BC_PORT);
        	System.out.println("SyncAgentActivator: Sending the broadcast packet");
			bcServer.sendPacket(packet);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Return computers real IP address. This is crap shoot since the returned address is 
	 * the last read non-loopback address. Better approad would be to return an array of addresses
	 * which are real, then client would work also on gateway device with multiple interfaces. 
	 * @return address of last real network interface
	 * @throws UnknownHostException
	 */
	private InetAddress getLocalAddress() throws UnknownHostException {
		InetAddress localAddr = InetAddress.getLocalHost();
		//String localAddr = locallAddr.getHostAddress();
		
		if (localAddr.isLoopbackAddress())
		{
			System.out.println("Error local address is loopback!!");
			NetworkInterface iface = null;
			try {
				for(Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();ifaces.hasMoreElements();){
					iface = (NetworkInterface)ifaces.nextElement();
					System.out.println("Interface:"+ iface.getDisplayName());
					InetAddress ia = null;
					for(Enumeration ips = iface.getInetAddresses();ips.hasMoreElements();){
						ia = (InetAddress)ips.nextElement();
						if (!ia.isLoopbackAddress())
						{
							System.out.println("Found non loopback addr: " + ia.toString());
							localAddr = ia;
							//localAddr = ia.getHostAddress();
						}
						System.out.println(ia.getCanonicalHostName()+" "+ ia.getHostAddress());
					}
				}
			} catch (SocketException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			
		}
		return localAddr;
	}
	
	/**
	 * Get broadcast address of the subnet to which the computer is registered.
	 * @return
	 * @throws UnknownHostException
	 */
	private InetAddress getBroadCastAddress() throws UnknownHostException {
		
		try {
			Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
			while (interfaces.hasMoreElements()) {
				NetworkInterface networkInterface = interfaces.nextElement();
				if (networkInterface.isLoopback())
					continue;    // Don't want to broadcast to the loopback interface
				for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
					InetAddress broadcast = interfaceAddress.getBroadcast();
					if (broadcast == null)
						continue;
			    
			    	System.out.println("Found broadcast address: " + broadcast.toString());
			    	return broadcast;
			    
				}
			}
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
		}
		
		return InetAddress.getByName("255.255.255.255"); // ei näin
		
		
//		return InetAddress.getByName("130.188.95.255");
//		String localAddr = getLocalAddress().getHostAddress();
//        
//        byte[] addrBytes = InetAddress.getByName(localAddr).getAddress();
//        System.out.println("SyncAgentActivator: local address is "+localAddr);
//        
//        String [] temp = localAddr.split(".");
//        System.out.println("SyncAgentActivator: local address is "+addrBytes[0]+" array idx "+ addrBytes.length);
//        /* 
//         * A-class IPv4 
//         */
//        if (addrBytes[0] < (byte)128)
//        {
//        	byte [] mask = { (byte)255, 0, 0, 0 };
//        	for (int i=0; i < 4; i++) {
//  	          addrBytes[i] |= ((byte)0xFF) ^ mask[i];
//  	        }
//        }
//        /*
//         * B-class IPv4
//         */
//        else if (addrBytes[0] < (byte)192)
//        {
//        	byte [] mask = { (byte)255, (byte)255, 0, 0 };
//        	for (int i=0; i < 4; i++) {
//  	          addrBytes[i] |= ((byte)0xFF) ^ mask[i];
//  	        }
//        }
//        /*
//         * C-class IPv4
//         */
//        else 
//        {
//        	byte [] mask = { (byte)255, (byte)255, (byte)255, 0 };
//        	for (int i=0; i < 4; i++) {
//  	          addrBytes[i] |= ((byte)0xFF) ^ mask[i];
//  	        }
//        }
//        
//        InetAddress bcastAddr = InetAddress.getByAddress(addrBytes);
//        //InetAddress bcastAddr = InetAddress.getByName(localAddr);
//        System.out.println("SyncAgentActivator: broadcast address is "+bcastAddr.getHostAddress());
//        return bcastAddr;
	}

	/**
	 * Publish events to clients. 
	 * @param event
	 */
	public void pushEvents(String event) {
		Socket clientSocket = null;
		PrintWriter out = null;
		String message = "event;"+event;
		if (!checkLocalEvent(event))
		{
			System.out.println("Not forwarding a remote event");
			return;
		}
		System.out.println("Pushing to "+clientList.size()+" clients");
		for (int i = 0; i < clientList.size(); i++)
		{
			System.out.println("Pushing to TCP/IP client in: "+ clientList.get(i).getHost() + " port "+ clientList.get(i).getPort());
			try {
				clientSocket = new Socket(clientList.get(i).getHost(),clientList.get(i).getPort());
				out = new PrintWriter(clientSocket.getOutputStream(), true);
				System.out.println("Send message: "+message);
				out.println(message);
				out.close();
				clientSocket.close();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 			

		}
	}
	
	private boolean checkLocalEvent(String event) {
		String[] temp = event.split(",");
		String[] cont;
		System.out.println("Check Local Event: Event has "+temp.length+" elements");
		for (int i = 0; i < temp.length; i++)
		{
			cont = temp[i].split("=");
			if (cont[0].compareTo("remote")==0)
			{
				return false;
			}
		}
		return true;
	}

	/**
	 * Interpret messages from clients. This might have to be removed if run-method blocks the thread. 
	 * @param event
	 */
	public void interpretMessage(String input2) {
		String[] inputs = input2.split(";");
		System.out.println("Trying to interpret received message " + input2);
		if (inputs[0].equalsIgnoreCase("register"))
		{
			try {
				registerClient(new URI(inputs[1]));
			}
			catch (URISyntaxException e)
			{
				System.out.println("Mallformed client URI: "+ inputs[1]);
			}
		}
		else if(inputs[0].equalsIgnoreCase("deregister")) {
			try {
				deRegisterClient(new URI(inputs[1]));
			}
			catch (URISyntaxException e)
			{
				System.out.println("Mallformed client URI: "+ inputs[1]);
			}
		}
		else if(inputs[0].equalsIgnoreCase("event")) {
			
			String event = "";
			for (int i = 1; i < inputs.length; i++)
			{
				event += inputs[i] + ";";
			}
			System.out.println("InterpretMessage got new event: " + event);
			
			localSyncAgent.publishEvents(event);
		}
		
	}
	
	int findClient(URI addr)
	{
		int found = -1;
		for (int i=0; i < clientList.size(); i++)
		{
			if (addr.equals(clientList.get(i)))
				return i;
		}
		return found;
	}

	public int deRegisterClient(URI cliAddr) {
		int index = findClient(cliAddr);
		
		if ( index != -1)
		{
			clientList.remove(index);
		}
		return 0;
	}


	public int registerClient(URI cliAddr) {
		if (findClient(cliAddr) == -1)
		{
			System.out.println("Adding client "+cliAddr.toString());
			clientList.add(cliAddr);
			return 1;
		}
		return 0;
	}


	/**
	 * Check if given client address is exsisting and add it if necessary
	 * @param inetAddress
	 */
	public void checkNewClient(InetAddress inetAddress) {
		try {
			URI newCliURI = new URI("osami://"+inetAddress.getHostAddress()+":"+Activator.SERVER_PORT);
			if (registerClient(newCliURI) == 1)
			{
				System.out.println("Added new client");
			}
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void shutdown() {
		bcServer.stopThread = true;
		serverThread.shutdown();
	}
}